<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>码云仓库提交记录分析工具</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
    <!-- 引入用于处理文件编码的库 -->
    <script src="https://cdn.jsdelivr.net/npm/encoding-japanese@2.0.0/encoding.min.js"></script>
</head>
<body class="bg-gray-100">
<div class="max-w-7xl mx-auto p-5">
    <header class="text-center mb-8">
        <h1 class="text-4xl font-bold text-gray-800 mb-2">码云仓库提交记录分析工具</h1>
        <p class="text-gray-600">导入学生仓库地址，获取提交记录并进行数据分析</p>
    </header>

    <div class="bg-white rounded-lg shadow-md p-6 mb-6">
        <div class="mb-6">
            <h2 class="text-2xl font-semibold text-gray-700 mb-4">
                <i class="fa fa-cloud-upload mr-2"></i>数据导入
            </h2>
            <div class="grid md:grid-cols-2 gap-6">
                <div>
                    <h3 class="text-xl font-medium text-gray-600 mb-3">
                        <i class="fa fa-file-text-o mr-2"></i>导入模板
                    </h3>
                    <p class="text-gray-500 mb-3">下载CSV模板，填写学生信息和仓库地址</p>
                    <button id="downloadTemplate" class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded transition duration-300 flex items-center">
                        <i class="fa fa-download mr-2"></i>下载模板
                    </button>
                </div>
                <div>
                    <h3 class="text-xl font-medium text-gray-600 mb-3">
                        <i class="fa fa-upload mr-2"></i>上传文件
                    </h3>
                    <p class="text-gray-500 mb-3">上传包含学生信息的CSV文件</p>
                    <input type="file" id="fileInput" accept=".csv" class="hidden" />
                    <label for="fileInput" class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded transition duration-300 cursor-pointer inline-block flex items-center">
                        <i class="fa fa-file-o mr-2"></i>选择文件
                    </label>
                    <div id="fileStatus" class="mt-2 text-sm text-gray-500"></div>
                </div>
            </div>
        </div>

        <!-- 添加私人令牌输入区域 -->
        <div class="mb-6">
            <h3 class="text-xl font-medium text-gray-600 mb-3">
                <i class="fa fa-key mr-2"></i>码云私人令牌 (Personal Access Token)
            </h3>
            <p class="text-gray-500 mb-3">
                为了访问码云API，请提供您的私人令牌。<a href="https://gitee.com/profile/personal_access_tokens" target="_blank" class="text-blue-500 hover:underline">点击这里获取令牌</a>
            </p>
            <div class="flex">
                <input type="password" id="giteeToken" placeholder="请输入您的码云私人令牌"
                       class="flex-1 px-4 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                <button id="saveToken" class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-r-md transition duration-300">
                    保存
                </button>
            </div>
            <div id="tokenStatus" class="mt-2 text-sm text-gray-500"></div>
        </div>

        <div class="mb-6">
            <h3 class="text-xl font-medium text-gray-600 mb-3">
                <i class="fa fa-users mr-2"></i>学生列表
            </h3>
            <div id="studentList" class="max-h-60 overflow-y-auto border border-gray-200 rounded-lg"></div>
        </div>

        <div class="text-center">
            <button id="fetchData" class="bg-green-500 hover:bg-green-600 text-white py-3 px-6 rounded transition duration-300 text-lg font-medium flex items-center mx-auto">
                <i class="fa fa-refresh mr-2"></i>获取提交记录
            </button>
        </div>
    </div>

    <div class="bg-white rounded-lg shadow-md p-6 mb-6">
        <h2 class="text-2xl font-semibold text-gray-700 mb-4">
            <i class="fa fa-bar-chart mr-2"></i>数据分析
        </h2>
        <div class="grid md:grid-cols-2 gap-6">
            <div>
                <h3 class="text-xl font-medium text-gray-600 mb-3">
                    <i class="fa fa-user mr-2"></i>学生提交统计
                </h3>
                <div class="h-80">
                    <canvas id="studentChart"></canvas>
                </div>
            </div>
            <div>
                <h3 class="text-xl font-medium text-gray-600 mb-3">
                    <i class="fa fa-calendar mr-2"></i>每日提交统计
                </h3>
                <div class="h-80">
                    <canvas id="dateChart"></canvas>
                </div>
            </div>
        </div>
    </div>

    <div class="bg-white rounded-lg shadow-md p-6">
        <div class="flex justify-between items-center mb-4">
            <h2 class="text-2xl font-semibold text-gray-700">
                <i class="fa fa-table mr-2"></i>提交记录详情
            </h2>
            <button id="exportCSV" class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded transition duration-300 flex items-center">
                <i class="fa fa-download mr-2"></i>导出CSV
            </button>
        </div>
        <div class="overflow-x-auto">
            <table class="min-w-full bg-white border border-gray-200">
                <thead>
                <tr class="bg-gray-100">
                    <th class="py-3 px-4 text-left text-gray-700 border-b">学生姓名</th>
                    <th class="py-3 px-4 text-left text-gray-700 border-b">提交时间</th>
                    <th class="py-3 px-4 text-left text-gray-700 border-b">提交注释</th>
                    <th class="py-3 px-4 text-left text-gray-700 border-b">仓库地址</th>
                </tr>
                </thead>
                <tbody id="commitTableBody">
                <!-- 提交记录将通过JavaScript动态添加 -->
                </tbody>
            </table>
        </div>
    </div>
</div>

<script>
        // 全局变量
        let students = [];
        let allCommits = [];
        let giteeToken = localStorage.getItem('giteeToken') || '';

        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', function() {
            initEventListeners();
            // 如果本地已保存token，显示出来
            if (giteeToken) {
                document.getElementById('giteeToken').value = giteeToken;
                document.getElementById('tokenStatus').textContent = '私人令牌已保存';
            }
        });

        // 初始化事件监听器
        function initEventListeners() {
            // 下载模板按钮
            document.getElementById('downloadTemplate').addEventListener('click', downloadTemplate);

            // 文件上传
            document.getElementById('fileInput').addEventListener('change', handleFileUpload);

            // 保存私人令牌
            document.getElementById('saveToken').addEventListener('click', savePersonalToken);

            // 获取提交记录按钮
            document.getElementById('fetchData').addEventListener('click', fetchAllCommits);

            // 导出CSV按钮
            document.getElementById('exportCSV').addEventListener('click', exportToCSV);
        }

        // 下载CSV模板
        function downloadTemplate() {
            // 使用UTF-8 BOM编码，解决Excel打开乱码问题
            const content = "\ufeff姓名,学号,仓库地址\n张三,2023001,https://gitee.com/student1/project\n李四,2023002,https://gitee.com/student2/project";
            const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.setAttribute('href', url);
            link.setAttribute('download', 'students_template.csv');
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }

        // 处理文件上传
        function handleFileUpload(event) {
            const file = event.target.files[0];
            if (!file) return;

            const fileStatus = document.getElementById('fileStatus');
            fileStatus.textContent = `正在解析文件: ${file.name}`;

            const reader = new FileReader();
            // 读取文件为二进制数据
            reader.readAsArrayBuffer(file);

            reader.onload = function(e) {
                try {
                    const arrayBuffer = e.target.result;
                    // 使用encoding-japanese库检测并转换编码
                    const content = convertEncoding(arrayBuffer);

                    students = parseCSV(content);
                    displayStudentList();
                    fileStatus.textContent = `成功解析 ${students.length} 名学生信息`;
                } catch (error) {
                    fileStatus.textContent = `解析失败: ${error.message}`;
                    console.error(error);
                }
            };

            reader.onerror = function() {
                fileStatus.textContent = `文件读取失败: ${reader.error}`;
            };
        }

        // 检测并转换文件编码
        function convertEncoding(arrayBuffer) {
            const uint8Array = new Uint8Array(arrayBuffer);
            // 检测编码
            const encoding = Encoding.detect(uint8Array);
            console.log('检测到的文件编码:', encoding);

            // 转换为UTF-8字符串
            // 常见的中文编码是'GBK', 'gb2312', 'GB18030'
            if (['GBK', 'gb2312', 'GB18030'].includes(encoding)) {
                return Encoding.convert(uint8Array, {
                    to: 'UTF8',
                    from: 'GBK'
                });
            }

            // 如果是UTF-8带BOM，去掉BOM
            if (encoding === 'UTF8' && uint8Array[0] === 0xef && uint8Array[1] === 0xbb && uint8Array[2] === 0xbf) {
                return new TextDecoder('utf-8').decode(uint8Array.slice(3));
            }

            // 默认使用UTF-8解码
            return new TextDecoder('utf-8').decode(uint8Array);
        }

        // 解析CSV内容
        function parseCSV(content) {
            const lines = content.split('\n');
            const students = [];

            // 跳过标题行
            for (let i = 1; i < lines.length; i++) {
                const line = lines[i].trim();
                if (line === '') continue;

                // 使用逗号分割，但要考虑引号内的逗号
                const parts = parseCSVLine(line);
                if (parts.length >= 3) {
                    students.push({
                        name: parts[0].trim(),
                        id: parts[1].trim(),
                        repo: parts[2].trim()
                    });
                }
            }

            return students;
        }

        // 解析单行CSV，处理引号内的逗号
        function parseCSVLine(line) {
            const parts = [];
            let currentPart = '';
            let inQuotes = false;
            let quoteChar = '';

            for (let i = 0; i < line.length; i++) {
                const char = line[i];

                if (char === '"' || char === "'") {
                    if (!inQuotes) {
                        inQuotes = true;
                        quoteChar = char;
                    } else if (i < line.length - 1 && line[i + 1] === char) {
                        // 处理双引号 "" 或 ''
                        currentPart += char;
                        i++; // 跳过下一个引号
                    } else if (line[i + 1] === ',' || i === line.length - 1) {
                        // 引号结束
                        inQuotes = false;
                    } else {
                        // 引号内的引号，直接添加
                        currentPart += char;
                    }
                } else if (char === ',' && !inQuotes) {
                    parts.push(currentPart);
                    currentPart = '';
                } else {
                    currentPart += char;
                }
            }

            parts.push(currentPart);
            return parts;
        }

        // 显示学生列表
        function displayStudentList() {
            const studentList = document.getElementById('studentList');
            studentList.innerHTML = '';

            if (students.length === 0) {
                studentList.innerHTML = '<p class="p-3 text-gray-500">未导入任何学生信息</p>';
                return;
            }

            students.forEach(student => {
                const item = document.createElement('div');
                item.className = 'p-3 border-b border-gray-200 flex justify-between items-center';
                item.innerHTML = `
                    <div>
                        <span class="font-medium">${student.name}</span>
                        <span class="text-gray-500 text-sm ml-2">(${student.id})</span>
                    </div>
                    <div class="text-sm text-gray-600 break-all max-w-xs">${student.repo}</div>
                `;
                studentList.appendChild(item);
            });
        }

        // 保存私人令牌到本地存储
        function savePersonalToken() {
            const tokenInput = document.getElementById('giteeToken');
            const token = tokenInput.value.trim();
            const tokenStatus = document.getElementById('tokenStatus');

            if (!token) {
                tokenStatus.textContent = '请输入私人令牌';
                tokenStatus.className = 'mt-2 text-sm text-red-500';
                return;
            }

            localStorage.setItem('giteeToken', token);
            giteeToken = token;

            tokenStatus.textContent = '私人令牌已保存';
            tokenStatus.className = 'mt-2 text-sm text-green-500';
        }

        // 获取所有学生的提交记录
        async function fetchAllCommits() {
            if (students.length === 0) {
                alert('请先导入学生信息');
                return;
            }

            if (!giteeToken) {
                alert('请先输入并保存您的码云私人令牌');
                document.getElementById('giteeToken').focus();
                return;
            }

            const fetchButton = document.getElementById('fetchData');
            fetchButton.disabled = true;
            fetchButton.innerHTML = '<i class="fa fa-spinner fa-spin mr-2"></i>获取中...';

            allCommits = [];

            try {
                for (const student of students) {
                    const commits = await fetchCommits(student);
                    allCommits = allCommits.concat(commits);

                    // 避免请求过于频繁
                    await new Promise(resolve => setTimeout(resolve, 1000));
                }

                displayCommits();
                generateCharts();

                fetchButton.innerHTML = '<i class="fa fa-check mr-2"></i>获取成功';
                setTimeout(() => {
                    fetchButton.disabled = false;
                    fetchButton.innerHTML = '<i class="fa fa-refresh mr-2"></i>获取提交记录';
                }, 2000);
            } catch (error) {
                console.error('获取提交记录失败:', error);
                alert('获取提交记录失败: ' + error.message);

                fetchButton.disabled = false;
                fetchButton.innerHTML = '<i class="fa fa-refresh mr-2"></i>获取提交记录';
            }
        }

        // 从码云API获取提交记录（支持分页获取所有记录）
        async function fetchCommits(student) {
            try {
                // 从URL中提取用户名和仓库名
                const repoMatch = student.repo.match(/gitee\.com\/([^\/]+)\/([^\/]+)/);
                if (!repoMatch) {
                    throw new Error(`无效的仓库地址: ${student.repo}`);
                }

                const owner = repoMatch[1];
                const repoName = repoMatch[2];

                const allCommitsForRepo = [];
                let page = 1;
                const perPage = 100; // 每页最大100条

                while (true) {
                    // 码云API地址，带分页参数
                    const apiUrl = `https://gitee.com/api/v5/repos/${owner}/${repoName}/commits?page=${page}&per_page=${perPage}`;

                    const response = await fetch(apiUrl, {
                        method: 'GET',
                        headers: {
                            'Authorization': `token ${giteeToken}`,
                            'Accept': 'application/json'
                        }
                    });

                    if (!response.ok) {
                        // 如果是404，可能是仓库不存在或私有且无权限
                        if (response.status === 404) {
                            console.warn(`仓库 ${student.repo} 未找到或无权限访问，跳过...`);
                            return [];
                        }
                        throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
                    }

                    const commits = await response.json();

                    // 如果返回的记录为空，说明已经获取完所有记录
                    if (commits.length === 0) {
                        break;
                    }

                    // 处理返回的提交记录
                    const processedCommits = commits.map(commit => ({
                        studentName: student.name,
                        studentId: student.id,
                        repo: student.repo,
                        sha: commit.sha,
                        message: commit.commit.message,
                        date: commit.commit.committer.date,
                        author: commit.commit.committer.name
                    }));

                    allCommitsForRepo.push(...processedCommits);

                    // 如果返回的记录数小于每页最大数，说明已经是最后一页
                    if (commits.length < perPage) {
                        break;
                    }

                    // 继续获取下一页
                    page++;
                }

                return allCommitsForRepo;
            } catch (error) {
                console.error(`获取 ${student.name} 的提交记录失败:`, error);
                return [];
            }
        }

        // 显示提交记录
        function displayCommits() {
            const tableBody = document.getElementById('commitTableBody');
            tableBody.innerHTML = '';

            if (allCommits.length === 0) {
                tableBody.innerHTML = `
                    <tr>
                        <td colspan="4" class="py-3 px-4 text-center text-gray-500 border-b">
                            没有找到任何提交记录。请检查：
                            <br>1. 私人令牌是否正确且有足够权限
                            <br>2. 仓库地址是否正确
                            <br>3. 仓库是否为公开或您有权访问的私有仓库
                        </td>
                    </tr>
                `;
                return;
            }

            // 按日期降序排序
            allCommits.sort((a, b) => new Date(b.date) - new Date(a.date));

            allCommits.forEach(commit => {
                const row = document.createElement('tr');
                row.className = 'hover:bg-gray-50';

                // 格式化日期
                const date = new Date(commit.date);
                const formattedDate = date.toLocaleString('zh-CN', {
                    year: 'numeric',
                    month: '2-digit',
                    day: '2-digit',
                    hour: '2-digit',
                    minute: '2-digit',
                    second: '2-digit'
                });

                row.innerHTML = `
                    <td class="py-3 px-4 border-b">${commit.studentName}</td>
                    <td class="py-3 px-4 border-b">${formattedDate}</td>
                    <td class="py-3 px-4 border-b max-w-xs truncate" title="${commit.message}">${commit.message}</td>
                    <td class="py-3 px-4 border-b">
                        <a href="${commit.repo}" target="_blank" class="text-blue-500 hover:underline">
                            ${commit.repo}
                        </a>
                    </td>
                `;

                tableBody.appendChild(row);
            });
        }

        // 生成图表
        function generateCharts() {
            // 学生提交统计
            const studentData = {};
            allCommits.forEach(commit => {
                if (!studentData[commit.studentName]) {
                    studentData[commit.studentName] = 0;
                }
                studentData[commit.studentName]++;
            });

            const studentLabels = Object.keys(studentData);
            const studentCounts = studentLabels.map(name => studentData[name]);

            // 每日提交统计
            const dateData = {};
            allCommits.forEach(commit => {
                const date = new Date(commit.date).toISOString().split('T')[0];
                if (!dateData[date]) {
                    dateData[date] = 0;
                }
                dateData[date]++;
            });

            const dateLabels = Object.keys(dateData).sort();
            const dateCounts = dateLabels.map(date => dateData[date]);

            // 创建学生提交统计图
            const studentCtx = document.getElementById('studentChart').getContext('2d');
            // 如果已有图表实例，先销毁
            if (window.studentChartInstance) {
                window.studentChartInstance.destroy();
            }
            window.studentChartInstance = new Chart(studentCtx, {
                type: 'bar',
                data: {
                    labels: studentLabels,
                    datasets: [{
                        label: '提交次数',
                        data: studentCounts,
                        backgroundColor: 'rgba(54, 162, 235, 0.7)',
                        borderColor: 'rgba(54, 162, 235, 1)',
                        borderWidth: 1
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: {
                        y: {
                            beginAtZero: true,
                            title: {
                                display: true,
                                text: '提交次数'
                            }
                        },
                        x: {
                            title: {
                                display: true,
                                text: '学生姓名'
                            }
                        }
                    }
                }
            });

            // 创建每日提交统计图
            const dateCtx = document.getElementById('dateChart').getContext('2d');
            // 如果已有图表实例，先销毁
            if (window.dateChartInstance) {
                window.dateChartInstance.destroy();
            }
            window.dateChartInstance = new Chart(dateCtx, {
                type: 'bar',
                data: {
                    labels: dateLabels,
                    datasets: [{
                        label: '提交次数',
                        data: dateCounts,
                        backgroundColor: 'rgba(75, 192, 192, 0.7)',
                        borderColor: 'rgba(75, 192, 192, 1)',
                        borderWidth: 1
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: {
                        y: {
                            beginAtZero: true,
                            title: {
                                display: true,
                                text: '提交次数'
                            }
                        },
                        x: {
                            title: {
                                display: true,
                                text: '日期'
                            }
                        }
                    }
                }
            });
        }

        // 导出CSV文件
        function exportToCSV() {
            if (allCommits.length === 0) {
                alert('没有可导出的提交记录');
                return;
            }

            // 准备CSV内容，使用UTF-8 BOM避免Excel打开乱码
            let csvContent = "\ufeff学生姓名,学生学号,提交时间,提交注释,仓库地址\n";

            allCommits.forEach(commit => {
                const row = [
                    commit.studentName,
                    commit.studentId,
                    commit.date,
                    `"${commit.message.replace(/"/g, '""')}"`, // 处理引号
                    commit.repo
                ];
                csvContent += row.join(',') + "\n";
            });

            // 创建并下载文件
            const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.setAttribute('href', url);
            link.setAttribute('download', `gitee_commits_${new Date().toISOString().split('T')[0]}.csv`);
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    </script>
</body>
</html>